package main

import (
	"context"
	"fmt"
	"strings"

	"go.opentelemetry.io/otel/codes"
	"golang.org/x/sync/errgroup"

	"github.com/dagger/dagger/.dagger/internal/dagger"
)

type GoSDK struct {
	Dagger *DaggerDev // +private
}

// Lint the Go SDK
func (t GoSDK) Lint(ctx context.Context) (rerr error) {
	eg := errgroup.Group{}
	eg.Go(func() (rerr error) {
		ctx, span := Tracer().Start(ctx, "lint the go source")
		defer func() {
			if rerr != nil {
				span.SetStatus(codes.Error, rerr.Error())
			}
			span.End()
		}()
		return dag.
			Go(t.Dagger.Source()).
			Lint(ctx, dagger.GoLintOpts{Packages: []string{"sdk/go"}})
	})
	eg.Go(func() (rerr error) {
		ctx, span := Tracer().Start(ctx, "check that the generated client library is up-to-date")
		defer func() {
			if rerr != nil {
				span.SetStatus(codes.Error, rerr.Error())
			}
			span.End()
		}()
		before := t.Dagger.Source()
		after, err := t.Generate(ctx)
		if err != nil {
			return err
		}
		return dag.Dirdiff().AssertEqual(ctx, before, after, []string{"sdk/go"})
	})
	return eg.Wait()
}

// Test the Go SDK
func (t GoSDK) Test(ctx context.Context) (rerr error) {
	installer, err := t.Dagger.installer(ctx, "sdk")
	if err != nil {
		return err
	}

	output, err := t.Dagger.Go().Env().
		With(installer).
		WithWorkdir("sdk/go").
		WithExec([]string{"go", "test", "-v", "-skip=TestProvision", "./..."}).
		Stdout(ctx)
	if err != nil {
		err = fmt.Errorf("test failed: %w\n%s", err, output)
	}
	return err
}

// Regenerate the Go SDK API
func (t GoSDK) Generate(ctx context.Context) (*dagger.Directory, error) {
	installer, err := t.Dagger.installer(ctx, "sdk")
	if err != nil {
		return nil, err
	}

	generated := t.Dagger.Go().Env().
		With(installer).
		WithWorkdir("sdk/go").
		WithExec([]string{"go", "generate", "-v", "./..."}).
		WithExec([]string{"go", "mod", "tidy"}).
		Directory(".")
	return dag.Directory().WithDirectory("sdk/go", generated), nil
}

// Test the publishing process
func (t GoSDK) TestPublish(ctx context.Context, tag string) error {
	return t.Publish(
		ctx,
		tag,
		true,
		"https://github.com/dagger/dagger-go-sdk.git",
		"https://github.com/dagger/dagger.git",
		"dagger-ci",
		"hello@dagger.io",
		nil,
	)
}

// Publish the Go SDK
func (t GoSDK) Publish(
	ctx context.Context,
	tag string,

	// +optional
	dryRun bool,

	// +optional
	// +default="https://github.com/dagger/dagger-go-sdk.git"
	gitRepo string,
	// +optional
	// +default="https://github.com/dagger/dagger.git"
	gitRepoSource string,
	// +optional
	// +default="dagger-ci"
	gitUserName string,
	// +optional
	// +default="hello@dagger.io"
	gitUserEmail string,

	// +optional
	githubToken *dagger.Secret,
) error {
	version := strings.TrimPrefix(tag, "sdk/go/")

	if err := gitPublish(ctx, t.Dagger.Git, gitPublishOpts{
		sdk:          "go",
		source:       gitRepoSource,
		sourceTag:    tag,
		sourcePath:   "sdk/go/",
		sourceFilter: "if [ -f go.mod ]; then go mod edit -dropreplace github.com/dagger/dagger; fi",
		sourceEnv:    t.Dagger.Go().Env(),
		dest:         gitRepo,
		destTag:      version,
		username:     gitUserName,
		email:        gitUserEmail,
		githubToken:  githubToken,
		dryRun:       dryRun,
	}); err != nil {
		return err
	}

	return nil
}

// Bump the Go SDK's Engine dependency
func (t GoSDK) Bump(ctx context.Context, version string) (*dagger.Directory, error) {
	// trim leading v from version
	version = strings.TrimPrefix(version, "v")

	versionFile := fmt.Sprintf(`// Code generated by dagger. DO NOT EDIT.

package engineconn

const CLIVersion = %q
`, version)

	// NOTE: if you change this path, be sure to update .github/workflows/publish.yml so that
	// provision tests run whenever this file changes.
	dir := dag.Directory().WithNewFile("sdk/go/internal/engineconn/version.gen.go", versionFile)
	return dir, nil
}
